package t2jumble;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import Sequenic.T2.TrFile;
import Sequenic.T2.Msg.T2Exception;
import Sequenic.T2.Seq.Trace;

import com.reeltwo.jumble.Jumble;
import com.reeltwo.jumble.fast.MutationResult;

public class T2Jumble {
	/**
	 * Construct a trace file containing a list of traces, with auxiliary properties
	 * culled from a template trace file.
	 * @param template Existing trace file to copy auxiliary properties from
	 * @param traces List of traces to put into file
	 * @return TrFile containing 'traces' with properties of 'template'
	 */
	private static TrFile createTraceFile(TrFile template, Trace... traces) {
	    LinkedList<Trace> tracesList = new LinkedList<Trace>();
	    for (Trace trace : traces) {
	    	tracesList.add(trace);
	    }
	    List<Method> methods = Collections.emptyList();
	    TrFile trFile = new TrFile(template.CUTname, template.poolClassName, tracesList, methods);
	    trFile.classinvariantOwners = template.classinvariantOwners;
	    return trFile;
	}
	
	/**
	 * <p>Given a class, generates valid test cases for it using T2. Each test case is then fed into Jumble, which mutates 
	 * the target class and checks whether the mutation was caught by the generated test case ('caught', meaning the case
	 * became invalid as a result of the mutation).</p>
	 * <p>Each test case or 'trace' is assigned a score which is equal to the number of mutations that were caught by the trace.
	 * The test cases are then sorted according to their respective scores.  
	 * @param className			class under test
	 * @param tracesToGenerate 	number of valid test cases to generate and rank
	 * @param traceLength 		length (number of steps) in each generated trace
	 * @throws T2Exception 
	 */
	public static void generateSelectiveTrace(String className, int tracesToGenerate, int traceLength) throws T2Exception {
		// TODO: Allow specification of full range of T2 and Jumble parameters
		Jumble jumble = new Jumble();
		List<TraceResult> results = new ArrayList<TraceResult>();
		
		int mutationPoints = 0;
		TrFile allTraces = null;

		// Generate #(tracesToGenerate) traces and save them to a single file
		try {
		    Sequenic.T2.Main.Junit(className + " --lenexec=" + traceLength + " --savegoodtr="+tracesToGenerate+" --nmax=" + (tracesToGenerate * traceLength) + 1);
		    allTraces = TrFile.load(className+".tr");
		} catch (T2Exception e) {
		    throw new RuntimeException(e);
		}
		assert(allTraces != null);
		
		// Split the combined traces file into individual traces.
		// 		Matt: What is the advantage of generating all these temporary files versus reusing a single file?
		int i=0;
		for (Trace trace : allTraces.traces ) {
			if (trace.violating) {
				// FIXME: If a violating trace is generated, then the number of non-violating traces Jumbled will be < tracesToGenerate
				//		  Should it be assumed that the class is fully functional before using this tool? If so, then an exception should be
				//		  thrown when a violating case is generated.
				continue;
			}
		    createTraceFile(allTraces, trace).save(className+"-"+ i++ +".tr");
		}

		for (i=0; i<allTraces.traces.size(); i++) {
		    // Jumble the class, replaying same trace file for each mutation to see if 
		    // it catches the mutation. Traces will be ranked based on the number of 
		    // mutations that were reported as caught.
		    String traceName = className+"-"+i+".tr";
		    try {
		        jumble.runMain(new String[] { "--printer", JumbleRecorder.class.getName(), "-D","traceFile="+traceName, 
                        className, ReplayTest.class.getName() });
		    } catch (Exception e) {
		        throw new RuntimeException(e);
		    }
		    List<Integer> mutationsCaught = new ArrayList<Integer>();
		    MutationResult[] result = JumbleRecorder.getResults();
		    mutationPoints = result.length;
		    for (int mutationPoint = 0; mutationPoint < mutationPoints; mutationPoint++) {
		        if (result[mutationPoint].isPassed()) {
		            mutationsCaught.add(mutationPoint);
		        }
		    }
		    results.add(new TraceResult(allTraces.traces.get(i), mutationsCaught, mutationPoints));
		}
		
		// For now, just report on the scores of each trace.
		Collections.sort(results);
		for (i = 0; i < results.size(); i++) {
			System.out.println(String.format("TRACE %04d: %s", i + 1, results.get(i).toString()));
			String traceName = className+"-"+i+".tr";
			new File(traceName).delete();
		}
		// TODO: Combine the best n traces into a single trace file. (createTraceFile())
	}
}
